1   /*
2    * Copyright (c) 2000, 2006, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package sun.security.provider.certpath.ldap;
27  
28  import java.io.ByteArrayInputStream;
29  import java.io.IOException;
30  import java.math.BigInteger;
31  import java.net.URI;
32  import java.util.*;
33  import javax.naming.Context;
34  import javax.naming.NamingEnumeration;
35  import javax.naming.NamingException;
36  import javax.naming.NameNotFoundException;
37  import javax.naming.directory.Attribute;
38  import javax.naming.directory.Attributes;
39  import javax.naming.directory.BasicAttributes;
40  import javax.naming.directory.DirContext;
41  import javax.naming.directory.InitialDirContext;
42  
43  import java.security.*;
44  import java.security.cert.Certificate;
45  import java.security.cert.*;
46  import javax.security.auth.x500.X500Principal;
47  
48  import sun.misc.HexDumpEncoder;
49  import sun.security.provider.certpath.X509CertificatePair;
50  import sun.security.util.Cache;
51  import sun.security.util.Debug;
52  import sun.security.x509.X500Name;
53  import sun.security.action.GetPropertyAction;
54  
55  /**
56   * A <code>CertStore</code> that retrieves <code>Certificates</code> and
57   * <code>CRL</code>s from an LDAP directory, using the PKIX LDAP V2 Schema
58   * (RFC 2587):
59   * <a href="http://www.ietf.org/rfc/rfc2587.txt">
60   * http://www.ietf.org/rfc/rfc2587.txt</a>.
61   * <p>
62   * Before calling the {@link #engineGetCertificates engineGetCertificates} or
63   * {@link #engineGetCRLs engineGetCRLs} methods, the
64   * {@link #LDAPCertStore(CertStoreParameters)
65   * LDAPCertStore(CertStoreParameters)} constructor is called to create the
66   * <code>CertStore</code> and establish the DNS name and port of the LDAP
67   * server from which <code>Certificate</code>s and <code>CRL</code>s will be
68   * retrieved.
69   * <p>
70   * <b>Concurrent Access</b>
71   * <p>
72   * As described in the javadoc for <code>CertStoreSpi</code>, the
73   * <code>engineGetCertificates</code> and <code>engineGetCRLs</code> methods
74   * must be thread-safe. That is, multiple threads may concurrently
75   * invoke these methods on a single <code>LDAPCertStore</code> object
76   * (or more than one) with no ill effects. This allows a
77   * <code>CertPathBuilder</code> to search for a CRL while simultaneously
78   * searching for further certificates, for instance.
79   * <p>
80   * This is achieved by adding the <code>synchronized</code> keyword to the
81   * <code>engineGetCertificates</code> and <code>engineGetCRLs</code> methods.
82   * <p>
83   * This classes uses caching and requests multiple attributes at once to
84   * minimize LDAP round trips. The cache is associated with the CertStore
85   * instance. It uses soft references to hold the values to minimize impact
86   * on footprint and currently has a maximum size of 750 attributes and a
87   * 30 second default lifetime.
88   * <p>
89   * We always request CA certificates, cross certificate pairs, and ARLs in
90   * a single LDAP request when any one of them is needed. The reason is that
91   * we typically need all of them anyway and requesting them in one go can
92   * reduce the number of requests to a third. Even if we don't need them,
93   * these attributes are typically small enough not to cause a noticeable
94   * overhead. In addition, when the prefetchCRLs flag is true, we also request
95   * the full CRLs. It is currently false initially but set to true once any
96   * request for an ARL to the server returns an null value. The reason is
97   * that CRLs could be rather large but are rarely used. This implementation
98   * should improve performance in most cases.
99   *
100  * @see java.security.cert.CertStore
101  *
102  * @since       1.4
103  * @author      Steve Hanna
104  * @author      Andreas Sterbenz
105  */
106 public class LDAPCertStore extends CertStoreSpi {
107 
108     private static final Debug debug = Debug.getInstance("certpath");
109 
110     private final static boolean DEBUG = false;
111 
112     /**
113      * LDAP attribute identifiers.
114      */
115     private static final String USER_CERT = "userCertificate;binary";
116     private static final String CA_CERT = "cACertificate;binary";
117     private static final String CROSS_CERT = "crossCertificatePair;binary";
118     private static final String CRL = "certificateRevocationList;binary";
119     private static final String ARL = "authorityRevocationList;binary";
120     private static final String DELTA_CRL = "deltaRevocationList;binary";
121 
122     // Constants for various empty values
123     private final static String[] STRING0 = new String[0];
124 
125     private final static byte[][] BB0 = new byte[0][];
126 
127     private final static Attributes EMPTY_ATTRIBUTES = new BasicAttributes();
128 
129     // cache related constants
130     private final static int DEFAULT_CACHE_SIZE = 750;
131     private final static int DEFAULT_CACHE_LIFETIME = 30;
132 
133     private final static int LIFETIME;
134 
135     private final static String PROP_LIFETIME =
136                             "sun.security.certpath.ldap.cache.lifetime";
137 
138     static {
139         String s = AccessController.doPrivileged(
140                                 new GetPropertyAction(PROP_LIFETIME));
141         if (s != null) {
142             LIFETIME = Integer.parseInt(s); // throws NumberFormatException
143         } else {
144             LIFETIME = DEFAULT_CACHE_LIFETIME;
145         }
146     }
147 
148     /**
149      * The CertificateFactory used to decode certificates from
150      * their binary stored form.
151      */
152     private CertificateFactory cf;
153     /**
154      * The JNDI directory context.
155      */
156     private DirContext ctx;
157 
158     /**
159      * Flag indicating whether we should prefetch CRLs.
160      */
161     private boolean prefetchCRLs = false;
162 
163     private final Cache valueCache;
164 
165     private int cacheHits = 0;
166     private int cacheMisses = 0;
167     private int requests = 0;
168 
169     /**
170      * Creates a <code>CertStore</code> with the specified parameters.
171      * For this class, the parameters object must be an instance of
172      * <code>LDAPCertStoreParameters</code>.
173      *
174      * @param params the algorithm parameters
175      * @exception InvalidAlgorithmParameterException if params is not an
176      *   instance of <code>LDAPCertStoreParameters</code>
177      */
178     public LDAPCertStore(CertStoreParameters params)
179             throws InvalidAlgorithmParameterException {
180         super(params);
181         if (!(params instanceof LDAPCertStoreParameters))
182           throw new InvalidAlgorithmParameterException(
183             "parameters must be LDAPCertStoreParameters");
184 
185         LDAPCertStoreParameters lparams = (LDAPCertStoreParameters) params;
186 
187         // Create InitialDirContext needed to communicate with the server
188         createInitialDirContext(lparams.getServerName(), lparams.getPort());
189 
190         // Create CertificateFactory for use later on
191         try {
192             cf = CertificateFactory.getInstance("X.509");
193         } catch (CertificateException e) {
194             throw new InvalidAlgorithmParameterException(
195                 "unable to create CertificateFactory for X.509");
196         }
197         if (LIFETIME == 0) {
198             valueCache = Cache.newNullCache();
199         } else if (LIFETIME < 0) {
200             valueCache = Cache.newSoftMemoryCache(DEFAULT_CACHE_SIZE);
201         } else {
202             valueCache = Cache.newSoftMemoryCache(DEFAULT_CACHE_SIZE, LIFETIME);
203         }
204     }
205 
206     /**
207      * Returns an LDAP CertStore. This method consults a cache of
208      * CertStores (shared per JVM) using the LDAP server/port as a key.
209      */
210     private static final Cache certStoreCache = Cache.newSoftMemoryCache(185);
211     static synchronized CertStore getInstance(LDAPCertStoreParameters params)
212         throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
213         CertStore lcs = (CertStore) certStoreCache.get(params);
214         if (lcs == null) {
215             lcs = CertStore.getInstance("LDAP", params);
216             certStoreCache.put(params, lcs);
217         } else {
218             if (debug != null) {
219                 debug.println("LDAPCertStore.getInstance: cache hit");
220             }
221         }
222         return lcs;
223     }
224 
225     /**
226      * Create InitialDirContext.
227      *
228      * @param server Server DNS name hosting LDAP service
229      * @param port   Port at which server listens for requests
230      * @throws InvalidAlgorithmParameterException if creation fails
231      */
232     private void createInitialDirContext(String server, int port)
233             throws InvalidAlgorithmParameterException {
234         String url = "ldap://" + server + ":" + port;
235         Hashtable<String,Object> env = new Hashtable<String,Object>();
236         env.put(Context.INITIAL_CONTEXT_FACTORY,
237                 "com.sun.jndi.ldap.LdapCtxFactory");
238         env.put(Context.PROVIDER_URL, url);
239         try {
240             ctx = new InitialDirContext(env);
241             /*
242              * By default, follow referrals unless application has
243              * overridden property in an application resource file.
244              */
245             Hashtable<?,?> currentEnv = ctx.getEnvironment();
246             if (currentEnv.get(Context.REFERRAL) == null) {
247                 ctx.addToEnvironment(Context.REFERRAL, "follow");
248             }
249         } catch (NamingException e) {
250             if (debug != null) {
251                 debug.println("LDAPCertStore.engineInit about to throw "
252                     + "InvalidAlgorithmParameterException");
253                 e.printStackTrace();
254             }
255             Exception ee = new InvalidAlgorithmParameterException
256                 ("unable to create InitialDirContext using supplied parameters");
257             ee.initCause(e);
258             throw (InvalidAlgorithmParameterException)ee;
259         }
260     }
261 
262     /**
263      * Private class encapsulating the actual LDAP operations and cache
264      * handling. Use:
265      *
266      *   LDAPRequest request = new LDAPRequest(dn);
267      *   request.addRequestedAttribute(CROSS_CERT);
268      *   request.addRequestedAttribute(CA_CERT);
269      *   byte[][] crossValues = request.getValues(CROSS_CERT);
270      *   byte[][] caValues = request.getValues(CA_CERT);
271      *
272      * At most one LDAP request is sent for each instance created. If all
273      * getValues() calls can be satisfied from the cache, no request
274      * is sent at all. If a request is sent, all requested attributes
275      * are always added to the cache irrespective of whether the getValues()
276      * method is called.
277      */
278     private class LDAPRequest {
279 
280         private final String name;
281         private Map<String, byte[][]> valueMap;
282         private final List<String> requestedAttributes;
283 
284         LDAPRequest(String name) {
285             this.name = name;
286             requestedAttributes = new ArrayList<String>(5);
287         }
288 
289         String getName() {
290             return name;
291         }
292 
293         void addRequestedAttribute(String attrId) {
294             if (valueMap != null) {
295                 throw new IllegalStateException("Request already sent");
296             }
297             requestedAttributes.add(attrId);
298         }
299 
300         /**
301          * Gets one or more binary values from an attribute.
302          *
303          * @param name          the location holding the attribute
304          * @param attrId                the attribute identifier
305          * @return                      an array of binary values (byte arrays)
306          * @throws NamingException      if a naming exception occurs
307          */
308         byte[][] getValues(String attrId) throws NamingException {
309             if (DEBUG && ((cacheHits + cacheMisses) % 50 == 0)) {
310                 System.out.println("Cache hits: " + cacheHits + "; misses: "
311                         + cacheMisses);
312             }
313             String cacheKey = name + "|" + attrId;
314             byte[][] values = (byte[][])valueCache.get(cacheKey);
315             if (values != null) {
316                 cacheHits++;
317                 return values;
318             }
319             cacheMisses++;
320             Map<String, byte[][]> attrs = getValueMap();
321             values = attrs.get(attrId);
322             return values;
323         }
324 
325         /**
326          * Get a map containing the values for this request. The first time
327          * this method is called on an object, the LDAP request is sent,
328          * the results parsed and added to a private map and also to the
329          * cache of this LDAPCertStore. Subsequent calls return the private
330          * map immediately.
331          *
332          * The map contains an entry for each requested attribute. The
333          * attribute name is the key, values are byte[][]. If there are no
334          * values for that attribute, values are byte[0][].
335          *
336          * @return                      the value Map
337          * @throws NamingException      if a naming exception occurs
338          */
339         private Map<String, byte[][]> getValueMap() throws NamingException {
340             if (valueMap != null) {
341                 return valueMap;
342             }
343             if (DEBUG) {
344                 System.out.println("Request: " + name + ":" + requestedAttributes);
345                 requests++;
346                 if (requests % 5 == 0) {
347                     System.out.println("LDAP requests: " + requests);
348                 }
349             }
350             valueMap = new HashMap<String, byte[][]>(8);
351             String[] attrIds = requestedAttributes.toArray(STRING0);
352             Attributes attrs;
353             try {
354                 attrs = ctx.getAttributes(name, attrIds);
355             } catch (NameNotFoundException e) {
356                 // name does not exist on this LDAP server
357                 // treat same as not attributes found
358                 attrs = EMPTY_ATTRIBUTES;
359             }
360             for (String attrId : requestedAttributes) {
361                 Attribute attr = attrs.get(attrId);
362                 byte[][] values = getAttributeValues(attr);
363                 cacheAttribute(attrId, values);
364                 valueMap.put(attrId, values);
365             }
366             return valueMap;
367         }
368 
369         /**
370          * Add the values to the cache.
371          */
372         private void cacheAttribute(String attrId, byte[][] values) {
373             String cacheKey = name + "|" + attrId;
374             valueCache.put(cacheKey, values);
375         }
376 
377         /**
378          * Get the values for the given attribute. If the attribute is null
379          * or does not contain any values, a zero length byte array is
380          * returned. NOTE that it is assumed that all values are byte arrays.
381          */
382         private byte[][] getAttributeValues(Attribute attr)
383                 throws NamingException {
384             byte[][] values;
385             if (attr == null) {
386                 values = BB0;
387             } else {
388                 values = new byte[attr.size()][];
389                 int i = 0;
390                 NamingEnumeration<?> enum_ = attr.getAll();
391                 while (enum_.hasMore()) {
392                     Object obj = enum_.next();
393                     if (debug != null) {
394                         if (obj instanceof String) {
395                             debug.println("LDAPCertStore.getAttrValues() "
396                                 + "enum.next is a string!: " + obj);
397                         }
398                     }
399                     byte[] value = (byte[])obj;
400                     values[i++] = value;
401                 }
402             }
403             return values;
404         }
405 
406     }
407 
408     /*
409      * Gets certificates from an attribute id and location in the LDAP
410      * directory. Returns a Collection containing only the Certificates that
411      * match the specified CertSelector.
412      *
413      * @param name the location holding the attribute
414      * @param id the attribute identifier
415      * @param sel a CertSelector that the Certificates must match
416      * @return a Collection of Certificates found
417      * @throws CertStoreException       if an exception occurs
418      */
419     private Collection<X509Certificate> getCertificates(LDAPRequest request,
420         String id, X509CertSelector sel) throws CertStoreException {
421 
422         /* fetch encoded certs from storage */
423         byte[][] encodedCert;
424         try {
425             encodedCert = request.getValues(id);
426         } catch (NamingException namingEx) {
427             throw new CertStoreException(namingEx);
428         }
429 
430         int n = encodedCert.length;
431         if (n == 0) {
432             return Collections.<X509Certificate>emptySet();
433         }
434 
435         List<X509Certificate> certs = new ArrayList<X509Certificate>(n);
436         /* decode certs and check if they satisfy selector */
437         for (int i = 0; i < n; i++) {
438             ByteArrayInputStream bais = new ByteArrayInputStream(encodedCert[i]);
439             try {
440                 Certificate cert = cf.generateCertificate(bais);
441                 if (sel.match(cert)) {
442                   certs.add((X509Certificate)cert);
443                 }
444             } catch (CertificateException e) {
445                 if (debug != null) {
446                     debug.println("LDAPCertStore.getCertificates() encountered "
447                         + "exception while parsing cert, skipping the bad data: ");
448                     HexDumpEncoder encoder = new HexDumpEncoder();
449                     debug.println(
450                         "[ " + encoder.encodeBuffer(encodedCert[i]) + " ]");
451                 }
452             }
453         }
454 
455         return certs;
456     }
457 
458     /*
459      * Gets certificate pairs from an attribute id and location in the LDAP
460      * directory.
461      *
462      * @param name the location holding the attribute
463      * @param id the attribute identifier
464      * @return a Collection of X509CertificatePairs found
465      * @throws CertStoreException       if an exception occurs
466      */
467     private Collection<X509CertificatePair> getCertPairs(
468         LDAPRequest request, String id) throws CertStoreException {
469 
470         /* fetch the encoded cert pairs from storage */
471         byte[][] encodedCertPair;
472         try {
473             encodedCertPair = request.getValues(id);
474         } catch (NamingException namingEx) {
475             throw new CertStoreException(namingEx);
476         }
477 
478         int n = encodedCertPair.length;
479         if (n == 0) {
480             return Collections.<X509CertificatePair>emptySet();
481         }
482 
483         List<X509CertificatePair> certPairs =
484                                 new ArrayList<X509CertificatePair>(n);
485         /* decode each cert pair and add it to the Collection */
486         for (int i = 0; i < n; i++) {
487             try {
488                 X509CertificatePair certPair =
489                     X509CertificatePair.generateCertificatePair(encodedCertPair[i]);
490                 certPairs.add(certPair);
491             } catch (CertificateException e) {
492                 if (debug != null) {
493                     debug.println(
494                         "LDAPCertStore.getCertPairs() encountered exception "
495                         + "while parsing cert, skipping the bad data: ");
496                     HexDumpEncoder encoder = new HexDumpEncoder();
497                     debug.println(
498                         "[ " + encoder.encodeBuffer(encodedCertPair[i]) + " ]");
499                 }
500             }
501         }
502 
503         return certPairs;
504     }
505 
506     /*
507      * Looks at certificate pairs stored in the crossCertificatePair attribute
508      * at the specified location in the LDAP directory. Returns a Collection
509      * containing all Certificates stored in the forward component that match
510      * the forward CertSelector and all Certificates stored in the reverse
511      * component that match the reverse CertSelector.
512      * <p>
513      * If either forward or reverse is null, all certificates from the
514      * corresponding component will be rejected.
515      *
516      * @param name the location to look in
517      * @param forward the forward CertSelector (or null)
518      * @param reverse the reverse CertSelector (or null)
519      * @return a Collection of Certificates found
520      * @throws CertStoreException       if an exception occurs
521      */
522     private Collection<X509Certificate> getMatchingCrossCerts(
523             LDAPRequest request, X509CertSelector forward,
524             X509CertSelector reverse)
525             throws CertStoreException {
526         // Get the cert pairs
527         Collection<X509CertificatePair> certPairs =
528                                 getCertPairs(request, CROSS_CERT);
529 
530         // Find Certificates that match and put them in a list
531         ArrayList<X509Certificate> matchingCerts =
532                                         new ArrayList<X509Certificate>();
533         for (X509CertificatePair certPair : certPairs) {
534             X509Certificate cert;
535             if (forward != null) {
536                 cert = certPair.getForward();
537                 if ((cert != null) && forward.match(cert)) {
538                     matchingCerts.add(cert);
539                 }
540             }
541             if (reverse != null) {
542                 cert = certPair.getReverse();
543                 if ((cert != null) && reverse.match(cert)) {
544                     matchingCerts.add(cert);
545                 }
546             }
547         }
548         return matchingCerts;
549     }
550 
551     /**
552      * Returns a <code>Collection</code> of <code>Certificate</code>s that
553      * match the specified selector. If no <code>Certificate</code>s
554      * match the selector, an empty <code>Collection</code> will be returned.
555      * <p>
556      * It is not practical to search every entry in the LDAP database for
557      * matching <code>Certificate</code>s. Instead, the <code>CertSelector</code>
558      * is examined in order to determine where matching <code>Certificate</code>s
559      * are likely to be found (according to the PKIX LDAPv2 schema, RFC 2587).
560      * If the subject is specified, its directory entry is searched. If the
561      * issuer is specified, its directory entry is searched. If neither the
562      * subject nor the issuer are specified (or the selector is not an
563      * <code>X509CertSelector</code>), a <code>CertStoreException</code> is
564      * thrown.
565      *
566      * @param selector a <code>CertSelector</code> used to select which
567      *  <code>Certificate</code>s should be returned.
568      * @return a <code>Collection</code> of <code>Certificate</code>s that
569      *         match the specified selector
570      * @throws CertStoreException if an exception occurs
571      */
572     public synchronized Collection<X509Certificate> engineGetCertificates
573             (CertSelector selector) throws CertStoreException {
574         if (debug != null) {
575             debug.println("LDAPCertStore.engineGetCertificates() selector: "
576                 + String.valueOf(selector));
577         }
578 
579         if (selector == null) {
580             selector = new X509CertSelector();
581         }
582         if (!(selector instanceof X509CertSelector)) {
583             throw new CertStoreException("LDAPCertStore needs an X509CertSelector " +
584                                          "to find certs");
585         }
586         X509CertSelector xsel = (X509CertSelector) selector;
587         int basicConstraints = xsel.getBasicConstraints();
588         String subject = xsel.getSubjectAsString();
589         String issuer = xsel.getIssuerAsString();
590         HashSet<X509Certificate> certs = new HashSet<X509Certificate>();
591         if (debug != null) {
592             debug.println("LDAPCertStore.engineGetCertificates() basicConstraints: "
593                 + basicConstraints);
594         }
595 
596         // basicConstraints:
597         // -2: only EE certs accepted
598         // -1: no check is done
599         //  0: any CA certificate accepted
600         // >1: certificate's basicConstraints extension pathlen must match
601         if (subject != null) {
602             if (debug != null) {
603                 debug.println("LDAPCertStore.engineGetCertificates() "
604                     + "subject is not null");
605             }
606             LDAPRequest request = new LDAPRequest(subject);
607             if (basicConstraints > -2) {
608                 request.addRequestedAttribute(CROSS_CERT);
609                 request.addRequestedAttribute(CA_CERT);
610                 request.addRequestedAttribute(ARL);
611                 if (prefetchCRLs) {
612                     request.addRequestedAttribute(CRL);
613                 }
614             }
615             if (basicConstraints < 0) {
616                 request.addRequestedAttribute(USER_CERT);
617             }
618 
619             if (basicConstraints > -2) {
620                 certs.addAll(getMatchingCrossCerts(request, xsel, null));
621                 if (debug != null) {
622                     debug.println("LDAPCertStore.engineGetCertificates() after "
623                         + "getMatchingCrossCerts(subject,xsel,null),certs.size(): "
624                         + certs.size());
625                 }
626                 certs.addAll(getCertificates(request, CA_CERT, xsel));
627                 if (debug != null) {
628                     debug.println("LDAPCertStore.engineGetCertificates() after "
629                         + "getCertificates(subject,CA_CERT,xsel),certs.size(): "
630                         + certs.size());
631                 }
632             }
633             if (basicConstraints < 0) {
634                 certs.addAll(getCertificates(request, USER_CERT, xsel));
635                 if (debug != null) {
636                     debug.println("LDAPCertStore.engineGetCertificates() after "
637                         + "getCertificates(subject,USER_CERT, xsel),certs.size(): "
638                         + certs.size());
639                 }
640             }
641         } else {
642             if (debug != null) {
643                 debug.println
644                     ("LDAPCertStore.engineGetCertificates() subject is null");
645             }
646             if (basicConstraints == -2) {
647                 throw new CertStoreException("need subject to find EE certs");
648             }
649             if (issuer == null) {
650                 throw new CertStoreException("need subject or issuer to find certs");
651             }
652         }
653         if (debug != null) {
654             debug.println("LDAPCertStore.engineGetCertificates() about to "
655                 + "getMatchingCrossCerts...");
656         }
657         if ((issuer != null) && (basicConstraints > -2)) {
658             LDAPRequest request = new LDAPRequest(issuer);
659             request.addRequestedAttribute(CROSS_CERT);
660             request.addRequestedAttribute(CA_CERT);
661             request.addRequestedAttribute(ARL);
662             if (prefetchCRLs) {
663                 request.addRequestedAttribute(CRL);
664             }
665 
666             certs.addAll(getMatchingCrossCerts(request, null, xsel));
667             if (debug != null) {
668                 debug.println("LDAPCertStore.engineGetCertificates() after "
669                     + "getMatchingCrossCerts(issuer,null,xsel),certs.size(): "
670                     + certs.size());
671             }
672             certs.addAll(getCertificates(request, CA_CERT, xsel));
673             if (debug != null) {
674                 debug.println("LDAPCertStore.engineGetCertificates() after "
675                     + "getCertificates(issuer,CA_CERT,xsel),certs.size(): "
676                     + certs.size());
677             }
678         }
679         if (debug != null) {
680             debug.println("LDAPCertStore.engineGetCertificates() returning certs");
681         }
682         return certs;
683     }
684 
685     /*
686      * Gets CRLs from an attribute id and location in the LDAP directory.
687      * Returns a Collection containing only the CRLs that match the
688      * specified CRLSelector.
689      *
690      * @param name the location holding the attribute
691      * @param id the attribute identifier
692      * @param sel a CRLSelector that the CRLs must match
693      * @return a Collection of CRLs found
694      * @throws CertStoreException       if an exception occurs
695      */
696     private Collection<X509CRL> getCRLs(LDAPRequest request, String id,
697             X509CRLSelector sel) throws CertStoreException {
698 
699         /* fetch the encoded crls from storage */
700         byte[][] encodedCRL;
701         try {
702             encodedCRL = request.getValues(id);
703         } catch (NamingException namingEx) {
704             throw new CertStoreException(namingEx);
705         }
706 
707         int n = encodedCRL.length;
708         if (n == 0) {
709             return Collections.<X509CRL>emptySet();
710         }
711 
712         List<X509CRL> crls = new ArrayList<X509CRL>(n);
713         /* decode each crl and check if it matches selector */
714         for (int i = 0; i < n; i++) {
715             try {
716                 CRL crl = cf.generateCRL(new ByteArrayInputStream(encodedCRL[i]));
717                 if (sel.match(crl)) {
718                     crls.add((X509CRL)crl);
719                 }
720             } catch (CRLException e) {
721                 if (debug != null) {
722                     debug.println("LDAPCertStore.getCRLs() encountered exception"
723                         + " while parsing CRL, skipping the bad data: ");
724                     HexDumpEncoder encoder = new HexDumpEncoder();
725                     debug.println("[ " + encoder.encodeBuffer(encodedCRL[i]) + " ]");
726                 }
727             }
728         }
729 
730         return crls;
731     }
732 
733     /**
734      * Returns a <code>Collection</code> of <code>CRL</code>s that
735      * match the specified selector. If no <code>CRL</code>s
736      * match the selector, an empty <code>Collection</code> will be returned.
737      * <p>
738      * It is not practical to search every entry in the LDAP database for
739      * matching <code>CRL</code>s. Instead, the <code>CRLSelector</code>
740      * is examined in order to determine where matching <code>CRL</code>s
741      * are likely to be found (according to the PKIX LDAPv2 schema, RFC 2587).
742      * If issuerNames or certChecking are specified, the issuer's directory
743      * entry is searched. If neither issuerNames or certChecking are specified
744      * (or the selector is not an <code>X509CRLSelector</code>), a
745      * <code>CertStoreException</code> is thrown.
746      *
747      * @param selector A <code>CRLSelector</code> used to select which
748      *  <code>CRL</code>s should be returned. Specify <code>null</code>
749      *  to return all <code>CRL</code>s.
750      * @return A <code>Collection</code> of <code>CRL</code>s that
751      *         match the specified selector
752      * @throws CertStoreException if an exception occurs
753      */
754     public synchronized Collection<X509CRL> engineGetCRLs(CRLSelector selector)
755             throws CertStoreException {
756         if (debug != null) {
757             debug.println("LDAPCertStore.engineGetCRLs() selector: "
758                 + selector);
759         }
760         // Set up selector and collection to hold CRLs
761         if (selector == null) {
762             selector = new X509CRLSelector();
763         }
764         if (!(selector instanceof X509CRLSelector)) {
765             throw new CertStoreException("need X509CRLSelector to find CRLs");
766         }
767         X509CRLSelector xsel = (X509CRLSelector) selector;
768         HashSet<X509CRL> crls = new HashSet<X509CRL>();
769 
770         // Look in directory entry for issuer of cert we're checking.
771         Collection<Object> issuerNames;
772         X509Certificate certChecking = xsel.getCertificateChecking();
773         if (certChecking != null) {
774             issuerNames = new HashSet<Object>();
775             X500Principal issuer = certChecking.getIssuerX500Principal();
776             issuerNames.add(issuer.getName(X500Principal.RFC2253));
777         } else {
778             // But if we don't know which cert we're checking, try the directory
779             // entries of all acceptable CRL issuers
780             issuerNames = xsel.getIssuerNames();
781             if (issuerNames == null) {
782                 throw new CertStoreException("need issuerNames or certChecking to "
783                     + "find CRLs");
784             }
785         }
786         for (Object nameObject : issuerNames) {
787             String issuerName;
788             if (nameObject instanceof byte[]) {
789                 try {
790                     X500Principal issuer = new X500Principal((byte[])nameObject);
791                     issuerName = issuer.getName(X500Principal.RFC2253);
792                 } catch (IllegalArgumentException e) {
793                     continue;
794                 }
795             } else {
796                 issuerName = (String)nameObject;
797             }
798             // If all we want is CA certs, try to get the (probably shorter) ARL
799             Collection<X509CRL> entryCRLs = Collections.<X509CRL>emptySet();
800             if (certChecking == null || certChecking.getBasicConstraints() != -1) {
801                 LDAPRequest request = new LDAPRequest(issuerName);
802                 request.addRequestedAttribute(CROSS_CERT);
803                 request.addRequestedAttribute(CA_CERT);
804                 request.addRequestedAttribute(ARL);
805                 if (prefetchCRLs) {
806                     request.addRequestedAttribute(CRL);
807                 }
808                 try {
809                     entryCRLs = getCRLs(request, ARL, xsel);
810                     if (entryCRLs.isEmpty()) {
811                         // no ARLs found. We assume that means that there are
812                         // no ARLs on this server at all and prefetch the CRLs.
813                         prefetchCRLs = true;
814                     } else {
815                         crls.addAll(entryCRLs);
816                     }
817                 } catch (CertStoreException e) {
818                     if (debug != null) {
819                         debug.println("LDAPCertStore.engineGetCRLs non-fatal error "
820                             + "retrieving ARLs:" + e);
821                         e.printStackTrace();
822                     }
823                 }
824             }
825             // Otherwise, get the CRL
826             // if certChecking is null, we don't know if we should look in ARL or CRL
827             // attribute, so check both for matching CRLs.
828             if (entryCRLs.isEmpty() || certChecking == null) {
829                 LDAPRequest request = new LDAPRequest(issuerName);
830                 request.addRequestedAttribute(CRL);
831                 entryCRLs = getCRLs(request, CRL, xsel);
832                 crls.addAll(entryCRLs);
833             }
834         }
835         return crls;
836     }
837 
838     // converts an LDAP URI into LDAPCertStoreParameters
839     static LDAPCertStoreParameters getParameters(URI uri) {
840         String host = uri.getHost();
841         if (host == null) {
842             return new SunLDAPCertStoreParameters();
843         } else {
844             int port = uri.getPort();
845             return (port == -1
846                     ? new SunLDAPCertStoreParameters(host)
847                     : new SunLDAPCertStoreParameters(host, port));
848         }
849     }
850 
851     /*
852      * Subclass of LDAPCertStoreParameters with overridden equals/hashCode
853      * methods. This is necessary because the parameters are used as
854      * keys in the LDAPCertStore cache.
855      */
856     private static class SunLDAPCertStoreParameters
857         extends LDAPCertStoreParameters {
858 
859         private volatile int hashCode = 0;
860 
861         SunLDAPCertStoreParameters(String serverName, int port) {
862             super(serverName, port);
863         }
864         SunLDAPCertStoreParameters(String serverName) {
865             super(serverName);
866         }
867         SunLDAPCertStoreParameters() {
868             super();
869         }
870         public boolean equals(Object obj) {
871             if (!(obj instanceof LDAPCertStoreParameters)) {
872                 return false;
873             }
874             LDAPCertStoreParameters params = (LDAPCertStoreParameters) obj;
875             return (getPort() == params.getPort() &&
876                     getServerName().equalsIgnoreCase(params.getServerName()));
877         }
878         public int hashCode() {
879             if (hashCode == 0) {
880                 int result = 17;
881                 result = 37*result + getPort();
882                 result = 37*result + getServerName().toLowerCase().hashCode();
883                 hashCode = result;
884             }
885             return hashCode;
886         }
887     }
888 
889     /*
890      * This inner class wraps an existing X509CertSelector and adds
891      * additional criteria to match on when the certificate's subject is
892      * different than the LDAP Distinguished Name entry. The LDAPCertStore
893      * implementation uses the subject DN as the directory entry for
894      * looking up certificates. This can be problematic if the certificates
895      * that you want to fetch have a different subject DN than the entry
896      * where they are stored. You could set the selector's subject to the
897      * LDAP DN entry, but then the resulting match would fail to find the
898      * desired certificates because the subject DNs would not match. This
899      * class avoids that problem by introducing a certSubject which should
900      * be set to the certificate's subject DN when it is different than
901      * the LDAP DN.
902      */
903     static class LDAPCertSelector extends X509CertSelector {
904 
905         private X500Principal certSubject;
906         private X509CertSelector selector;
907         private X500Principal subject;
908 
909         /**
910          * Creates an LDAPCertSelector.
911          *
912          * @param selector the X509CertSelector to wrap
913          * @param certSubject the subject DN of the certificate that you want
914          *      to retrieve via LDAP
915          * @param ldapDN the LDAP DN where the certificate is stored
916          */
917         LDAPCertSelector(X509CertSelector selector, X500Principal certSubject,
918             String ldapDN) throws IOException {
919             this.selector = selector == null ? new X509CertSelector() : selector;
920             this.certSubject = certSubject;
921             this.subject = new X500Name(ldapDN).asX500Principal();
922         }
923 
924         // we only override the get (accessor methods) since the set methods
925         // will not be invoked by the code that uses this LDAPCertSelector.
926         public X509Certificate getCertificate() {
927             return selector.getCertificate();
928         }
929         public BigInteger getSerialNumber() {
930             return selector.getSerialNumber();
931         }
932         public X500Principal getIssuer() {
933             return selector.getIssuer();
934         }
935         public String getIssuerAsString() {
936             return selector.getIssuerAsString();
937         }
938         public byte[] getIssuerAsBytes() throws IOException {
939             return selector.getIssuerAsBytes();
940         }
941         public X500Principal getSubject() {
942             // return the ldap DN
943             return subject;
944         }
945         public String getSubjectAsString() {
946             // return the ldap DN
947             return subject.getName();
948         }
949         public byte[] getSubjectAsBytes() throws IOException {
950             // return the encoded ldap DN
951             return subject.getEncoded();
952         }
953         public byte[] getSubjectKeyIdentifier() {
954             return selector.getSubjectKeyIdentifier();
955         }
956         public byte[] getAuthorityKeyIdentifier() {
957             return selector.getAuthorityKeyIdentifier();
958         }
959         public Date getCertificateValid() {
960             return selector.getCertificateValid();
961         }
962         public Date getPrivateKeyValid() {
963             return selector.getPrivateKeyValid();
964         }
965         public String getSubjectPublicKeyAlgID() {
966             return selector.getSubjectPublicKeyAlgID();
967         }
968         public PublicKey getSubjectPublicKey() {
969             return selector.getSubjectPublicKey();
970         }
971         public boolean[] getKeyUsage() {
972             return selector.getKeyUsage();
973         }
974         public Set<String> getExtendedKeyUsage() {
975             return selector.getExtendedKeyUsage();
976         }
977         public boolean getMatchAllSubjectAltNames() {
978             return selector.getMatchAllSubjectAltNames();
979         }
980         public Collection<List<?>> getSubjectAlternativeNames() {
981             return selector.getSubjectAlternativeNames();
982         }
983         public byte[] getNameConstraints() {
984             return selector.getNameConstraints();
985         }
986         public int getBasicConstraints() {
987             return selector.getBasicConstraints();
988         }
989         public Set<String> getPolicy() {
990             return selector.getPolicy();
991         }
992         public Collection<List<?>> getPathToNames() {
993             return selector.getPathToNames();
994         }
995 
996         public boolean match(Certificate cert) {
997             // temporarily set the subject criterion to the certSubject
998             // so that match will not reject the desired certificates
999             selector.setSubject(certSubject);
1000             boolean match = selector.match(cert);
1001             selector.setSubject(subject);
1002             return match;
1003         }
1004     }
1005 
1006     /**
1007      * This class has the same purpose as LDAPCertSelector except it is for
1008      * X.509 CRLs.
1009      */
1010     static class LDAPCRLSelector extends X509CRLSelector {
1011 
1012         private X509CRLSelector selector;
1013         private Collection<X500Principal> certIssuers;
1014         private Collection<X500Principal> issuers;
1015         private HashSet<Object> issuerNames;
1016 
1017         /**
1018          * Creates an LDAPCRLSelector.
1019          *
1020          * @param selector the X509CRLSelector to wrap
1021          * @param certIssuers the issuer DNs of the CRLs that you want
1022          *      to retrieve via LDAP
1023          * @param ldapDN the LDAP DN where the CRL is stored
1024          */
1025         LDAPCRLSelector(X509CRLSelector selector,
1026             Collection<X500Principal> certIssuers, String ldapDN)
1027             throws IOException {
1028             this.selector = selector == null ? new X509CRLSelector() : selector;
1029             this.certIssuers = certIssuers;
1030             issuerNames = new HashSet<Object>();
1031             issuerNames.add(ldapDN);
1032             issuers = new HashSet<X500Principal>();
1033             issuers.add(new X500Name(ldapDN).asX500Principal());
1034         }
1035         // we only override the get (accessor methods) since the set methods
1036         // will not be invoked by the code that uses this LDAPCRLSelector.
1037         public Collection<X500Principal> getIssuers() {
1038             // return the ldap DN
1039             return Collections.unmodifiableCollection(issuers);
1040         }
1041         public Collection<Object> getIssuerNames() {
1042             // return the ldap DN
1043             return Collections.unmodifiableCollection(issuerNames);
1044         }
1045         public BigInteger getMinCRL() {
1046             return selector.getMinCRL();
1047         }
1048         public BigInteger getMaxCRL() {
1049             return selector.getMaxCRL();
1050         }
1051         public Date getDateAndTime() {
1052             return selector.getDateAndTime();
1053         }
1054         public X509Certificate getCertificateChecking() {
1055             return selector.getCertificateChecking();
1056         }
1057         public boolean match(CRL crl) {
1058             // temporarily set the issuer criterion to the certIssuers
1059             // so that match will not reject the desired CRL
1060             selector.setIssuers(certIssuers);
1061             boolean match = selector.match(crl);
1062             selector.setIssuers(issuers);
1063             return match;
1064         }
1065     }
1066 }